package my.jypark.seminar;

import java.io.*;
import java.util.HashMap;

/**
 * Created with IntelliJ IDEA.
 * User: jypark
 * Date: 13. 10. 28
 * Time: 오후 3:18
 * To change this template use File | Settings | File Templates.
 */
public class Loader {
    static class Constant {
        byte type;
        short index1;
        short index2;
        String str;

        @Override
        public String toString() {
            return String.valueOf(type);
        }
    }

    static HashMap<Short, Constant> cmap = new HashMap<Short, Constant>();

    private static String getUTF8(DataInputStream ins) throws IOException {
        short len = ins.readShort();
        byte [] str = new byte [len];

        ins.read(str, 0, len);

        return new String(str);
    }

    private static void readMethod(DataInputStream ins) throws IOException {
        short count;

        System.out.println("------ method ---------");
        printAccessFlag(ins);

        System.out.println();

        System.out.println("name : " + cmap.get(ins.readShort()).str);
        System.out.println("signature : "+cmap.get(ins.readShort()).str);

        count = ins.readShort();
        System.out.println("attribute count : "+count);

        for (int i=0; i<count; i++) {
            System.out.println("# attr "+i+" start");
            Attribute attr = AttributeFactory.getReader(ins);

            System.out.println("attribute name index : "+attr.getName());
            System.out.println("attribute length : "+attr.getLen());
            System.out.println(attr);
            System.out.println("# attr "+i+" end");
        }
    }

    private static String getClassName(int classID) {
        Constant c = cmap.get((short)classID);
        return cmap.get(c.index1).str;
    }

    private static void readField(DataInputStream ins) throws IOException {
        short count;

        System.out.println("------ field ---------");
        printAccessFlag(ins);

        System.out.println();

        System.out.println("name : "+cmap.get(ins.readShort()).str);
        System.out.println("desc index : "+cmap.get(ins.readShort()).str);

        count = ins.readShort();
        System.out.println("attribute count : "+count);

        for (int i=0; i<count; i++) {
            Attribute attr = AttributeFactory.getReader(ins);
            System.out.println("attribute name : "+attr.getName());
            System.out.println("attribute length : "+attr.getLen());
        }
    }

    private static void printAccessFlag(DataInputStream ins) throws IOException {
        short flag = ins.readShort();
        System.out.print("access flag : ");

        if ((flag & 0x1) != 0) System.out.print("public ");
        else if ((flag & 0x2) != 0) System.out.print("private ");
        else if ((flag & 0x4) != 0) System.out.print("protected ");

        if ((flag & 0x8) != 0) System.out.print("static ");
        if ((flag & 0x10) != 0) System.out.print("final ");
    }

    public static void main(String [] args) {
        try {
            DataInputStream ins = new DataInputStream(new FileInputStream("Test.class"));

            // magic number
            System.out.format("0x%X\n", ins.readInt());

            // version
            System.out.println("minor ver : " + ins.readShort());
            System.out.println("major ver : "+ins.readShort());

            readConstantPool(ins);

            System.out.format("\naccess flag : 0x%X\n", ins.readShort());
            System.out.println("this class : " + getClassName(ins.readShort()));
            System.out.println("super class : " + getClassName(ins.readShort()));

            readInterfaces(ins);

            short count;

            count = ins.readShort();
            System.out.println("\nfield count : "+count);

            for (int i=0; i<count; i++)
                readField(ins);

            count = ins.readShort();
            System.out.println("\nmethod count : "+count);

            for (int i=0; i<count; i++)
                readMethod(ins);

            System.out.println("\n--- class attributes ---");
            count = ins.readShort();
            System.out.println("\nattribute count : "+count);

            for (int i=0; i<count; i++) {
                int len;

                System.out.println("\nattribute name index : "+cmap.get(ins.readShort()).str);
                len = ins.readInt();
                System.out.println("attribute length : "+len);

                if (len == 0) continue;
                else if (len == 2) {
                    System.out.println(cmap.get(ins.readShort()).str);
                }
                else {
                    byte [] data = new byte[len];
                    ins.read(data, 0, len);
                }
            }

            ins.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void readInterfaces(DataInputStream ins) throws IOException {
        short count;

        count = ins.readShort();
        System.out.println("\ninterface count : "+count);

        for (int i=0; i<count; i++)
            System.out.println("interface : " + getClassName(ins.readShort()));
    }

    private static void readConstantPool(DataInputStream ins) throws IOException {

        // constant pool count
        short count = ins.readShort();
        System.out.println("constant pool count : "+count);

        // Constant Pool
        for (int i=1; i<count; i++) {
            Constant c = new Constant();
            c.type = ins.readByte();

            System.out.printf("#%d type : %d\n", i, c.type);

            switch(c.type) {
                // UTF8 String
                case 1:
                    c.str = getUTF8(ins);
                    System.out.println(c.str);
                    break;
                // Long
                case 5:
                    System.out.println("value : "+ins.readLong());
                    break;

                case 7: // Class
                case 8: // String
                    c.index1 = ins.readShort();
                    System.out.println("index : "+c.index1);
                    break;
                case 9:  // Field
                case 10:  // Method
                case 11:  // interface method
                    c.index1 = ins.readShort();
                    c.index2 = ins.readShort();
                    System.out.println("index1 : "+c.index1);
                    System.out.println("index2 : "+c.index2);
                    break;
                case 12:  // name and type
                    c.index1 = ins.readShort();
                    c.index2 = ins.readShort();
                    System.out.println("index1 : "+c.index1);
                    System.out.println("index2 : "+c.index2);
                    break;

                default:
                    System.out.println("!!!!!");
            }
            System.out.println();

            cmap.put((short)i, c);
        }

        System.out.println("map count : "+cmap.size());
    }
}